import jsonBigint from 'json-bigint'
import { date as quasarDateUtils, Dialog } from 'quasar'
import moment from 'moment'
import boxErr from '../json/boxErr.json'
import _ from 'lodash'

const iconv = require('iconv-lite')
const atob = (typeof window !== 'undefined' && window.atob) || require('atob')
const btoa = (typeof window !== 'undefined' && window.btoa) || require('btoa')

// 产生一个hash值，只有数字，规则和java的hashcode规则相同
function hashCode (str) {
  let h = 0
  const len = str.length
  const t = 2147483648
  for (let i = 0; i < len; i++) {
    h = 31 * h + str.charCodeAt(i)
    if (h > 2147483647) {
      h %= t
    } // java int溢出则取模
  }
  return h
}

/**
 * 千分符显示数字
 * @param num 待显示数字
 * @param fixed 小数点保留位数
 * @returns {string}
 */
export function getNumer (num, fixed = 0) {
  if (fixed) {
    const newData = Number(num).toFixed(fixed)
    const integer = Number(parseInt(newData.split('.')[0])).toLocaleString()
    const decimal = newData.split('.')[1]
    return integer + '.' + decimal
  } else {
    // eslint-disable-next-line no-undef
    return Number(BigInt(num)).toLocaleString()
  }
}
/**
 *
 * @param {*} data
 * @param {*} type 默认为 --
 */
export function label (data, type = '--') {
  return _.isBoolean(data) || data || data + '' === '0' ? data : type
}
/**
 * start
 * 统一将数据转换为 '--'或者空
 * param { Object Array } 两种形式
 * 由于table后端返回null, ''  部分转化为0 '--' 同时需要返回
 * @param {*} retData 数据
 * @param {*} params keysData  keysType 特定数组, 特定type]
 * type 通用type ,column 转化依据数组
 * 如果正常的话转化 setLabel(data, {})
 */
export function setLabel (retData, { keysData = [], keysType = '--', type = '--', column = [] }) {
  if (_.isEmpty(retData)) return retData
  const tableData = !_.isEmpty(column) ? _.map(column, 'name') : Object.keys(retData[0])
  // 区异化的数组
  const difData = _.difference(tableData, keysData)
  const member = retData.map(v => {
    const item = v
    keysData.map(k => { item[k] = _.isBoolean(v[k]) || v[k] || v[k] + '' === '0' ? v[k] : keysType })
    difData.map(k => { item[k] = _.isBoolean(v[k]) || v[k] || v[k] + '' === '0' ? v[k] : type })
    return item
  })
  return member
}
/**
 * end
 */

// 因此使用下面的随机数函数，在时间戳上加随机数，保证hash化的结果差异会比较大
/**
 ** randomWord 产生任意长度随机字母数字组合
 ** randomFlag-是否任意长度 min-任意长度最小位[固定位数] max-任意长度最大位
 ** 用法  randomWord(false,6);规定位数 flash
 *      randomWord(true,3，6);长度不定，true
 * arr变量可以把其他字符加入，如以后需要小写字母，直接加入即可
 **/
function randomWord (randomFlag, min, max) {
  let str = '',
    range = min,
    arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
      'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
  // 随机产生
  if (randomFlag) {
    range = Math.round(Math.random() * (max - min)) + min
  }
  for (var i = 0; i < range; i++) {
    const pos = Math.round(Math.random() * (arr.length - 1))
    str += arr[pos]
  }
  return str
}

// 获取hashcode
export function gethashcode () {
  // 定义一个时间戳，计算与1970年相差的毫秒数  用来获得唯一时间
  const timestamp = (new Date()).valueOf()
  const myRandom = randomWord(false, 6)
  const hashcode = hashCode(myRandom + timestamp.toString())
  return hashcode
}

/**
 * 函数防抖
 * fun 需要延时执行的函数
 * delayTime 延时时间
 * **/
export function debounce (func, delay = 500) {
  let timer

  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

/**
 * fun 需要延时执行的函数
 * delayTime 延时时间
 * **/
export function throttle (fun, delayTime = 500) {
  let timer = null
  let firstTime = true
  return function (...args) {
    if (firstTime) {
      // 第一次加载
      fun.apply(this, args)
      firstTime = false
      return
    }
    if (timer) {
      // 定时器正在执行中，跳过
      return
    }
    timer = setTimeout(() => {
      clearTimeout(timer)
      timer = null
      fun.apply(this, args)
    }, delayTime)
  }
}

// 获取无 default 节点的组织结构树
class DefaultSchemesClearer {
  /**
   * 获取无 default 节点的组织结构树
   * <p>
   * 注意：新的组织结构树中，parentId、parentLevel、parentName、level 不会发生变化（i.e. 若父级是
   * default，则 parentId 依然指向该 default 节点），如果某节点的祖先节点中存在 default 节点，
   * 则在新的树中，该节点的这些字段可能不再准确，因此这些字段不应该被使用。这些字段仅为特殊情况备用。
   *
   * @param schemesTree 含 default 节点的完整的组织结构树
   * @return {[Array<{}>]} 去除了 default 节点，并重排后的组织结构树
   */
  clear (schemesTree) {
    let flatSchemes = this._flattenSchemesTree(schemesTree)
    // 列出当前 index 与实际父级 index 的 map
    let indexMap = []
    for (let curIndex = 0; curIndex < flatSchemes.length; curIndex++) {
      let curScheme = flatSchemes[curIndex]

      if (curScheme.isDefault) {
        // 跳过 default
        continue
      }

      // 寻找实际父级 index，并加入 map
      let factualParentIndex = this._findSchemeFactualParentIndex(flatSchemes, curIndex)
      indexMap.push({
        node: curIndex,
        parent: factualParentIndex
      })
    }
    return this._assembleTrimmedSchemesTree(flatSchemes, indexMap)
  }

  /**
   * 将扁平化的 schemes 按照 indexMap 组装成 schemes' tree，并去除其中的 default scheme
   *
   * @param flatSchemes 扁平化的 scheme 数组
   * @param indexMap {Array<{ child, parent }>} 父子节点 index map
   */
  _assembleTrimmedSchemesTree (flatSchemes, indexMap) {
    let tree = []

    indexMap.forEach(indices => {
      let node = flatSchemes[indices.node]
      let parent = flatSchemes[indices.parent]
      if (!parent) {
        // 当前 node 位于根级，则插入到 tree，结束
        tree.push(node)
        return
      }

      // 向 parent 的 children 中插入当前 node
      parent.children = parent.children || []
      parent.childrenCount = parent.childrenCount || 0
      parent.children.push(node)
      parent.childrenCount++
    })

    return tree
  }

  /**
   * 寻找指定 scheme 的实际父级在 flatSchemes 中的索引
   * <p>
   * 从该 scheme 所在的 index 开始向前寻找，若在前面未找到，则从当前 index 开始向后寻找
   * <p>
   * 由于扁平化后的 scheme 数组是符合树状结构的，因此这种查找方式效率较高
   *
   * @param flatSchemes 扁平化的 scheme 数组
   * @param childIndex 子级 scheme 在 flatSchemes 中的索引
   * @return {number} 实际父级在 flatSchemes 中的索引，若当前节点已经是根级，则返回 -1
   */
  _findSchemeFactualParentIndex (flatSchemes, childIndex) {
    let curScheme = flatSchemes[childIndex]

    if (curScheme.level === 1) {
      // 若当前已经是根节点，直接返回 -1
      return -1
    }

    // 向前查找
    let parentId = curScheme.parentId
    let parentLevel = curScheme.level - 1

    for (let i = childIndex - 1; i >= 0; i--) {
      let curNode = flatSchemes[i]
      if (curNode.id === parentId && curNode.level === parentLevel) {
        // 找到父级
        if (curNode.isDefault) {
          // 但父级是 default，查找该父级 scheme 的父级
          return this._findSchemeFactualParentIndex(flatSchemes, i)
        } else {
          // 且父级不是 default，返回该父级 index
          return i
        }
      }
    }

    // 向后查找
    for (let i = childIndex + 1; i < flatSchemes.length; i++) {
      let curNode = flatSchemes[i]
      if (curNode.id === parentId && curNode.level === parentLevel) {
        // 找到父级
        if (curNode.isDefault) {
          // 但父级是 default，查找该父级 scheme 的父级
          return this._findSchemeFactualParentIndex(flatSchemes, i)
        } else {
          // 且父级不是 default，返回该父级 index
          return i
        }
      }
    }
  }

  /**
   * 将树状组织结构转成扁平结构，并保留树状顺序
   *
   * @param schemesTree 树状组织结构
   * @returns {[]} 扁平化的组织结构
   */
  _flattenSchemesTree (schemesTree) {
    let flatArr = []

    // 递归平坦化每一级节点
    schemesTree.forEach(flattenNode)

    function flattenNode (node) {
      // 克隆当前 node，并去除其 children
      let simplifiedNode = Object.assign({}, node)
      delete simplifiedNode.children
      delete simplifiedNode.workCenters
      delete simplifiedNode.childrenCount
      // 插入到扁平数组中
      flatArr.push(simplifiedNode)

      // 继续扁平化 node 的 children
      if (node.workCenters instanceof Array) {
        // 设备组的下级数组的字段名不是 children 而是 workCenters
        node.workCenters.forEach(flattenNode)
      }
      if (node.children instanceof Array) {
        node.children.forEach(flattenNode)
      }
    }

    return flatArr
  }
}

export const defaultSchemesClearer = new DefaultSchemesClearer()

/**
 * 输入框验证
 * 注意，在使用replace验证方式时，type最好是默认
 * **/
// 只能输入正整数
export function positiveInteger (num) {
  return num.replace(/\D/g, '')
}

// 是能输入保留两位小数的数
export function numToFix2 (num) {
  return num.replace(/^\D*([1-9]\d*\.?\d{0,2})?.*$/, '$1')
}
export function numToFix4 (num) {
  return num.replace(/^\D*([0-9]\d*\.?\d{0,4})?.*$/, '$1')
}

/**
 * 判断是否是图片文件、word文件、视频文件、音乐文件、快捷方式文件、压缩包文件
 * fileName 文件名称
 * **/
export function matchFileType (fileName) {
  let suffixIndex = fileName.lastIndexOf('.')
  let suffix = fileName.substring(suffixIndex + 1).toUpperCase()
  var matchList = [
    'png', 'jpg', 'jpeg', 'bmp', 'rle', 'dib', 'gif', 'psd', 'pdd', 'ai', 'pic', 'did', 'tif',
    'tiff', 'tga', 'ico', 'raw', // 图片
    'xls', 'xlsx', 'doc', 'docx', 'pdf', 'ppt', 'rtf', // word文件
    'avi', 'mov', 'rm', 'ram', '3gp', 'flv', 'mpg', 'mpeg', 'rmvb', 'mp4', 'm2v', 'mkv', 'mov',
    'swf', // 视频文件
    'mp3', 'wav', 'wmv', 'wma', 'amr', 'mid', 'cda', 'vqf', // 音频文件
    'link', 'lnk', // 快捷方式
    'rar', 'zip', 'arj', 'z', // 压缩包
    'exe', 'pptx', 'rp', 'xmind'
  ]
  let result = ''
  result = matchList.some(function (item) {
    return item === suffix.toLowerCase()
  })
  if (result) {
    return true
  }
  return false
}
/**
 * 文件上传 重命名 新建文件类型规则
 * @param {*} fileName
 */
export function isUploadFile (fileName) {
  let suffixIndex = fileName.lastIndexOf('.')
  if (suffixIndex === -1) {
    return false
  }
  let suffix = fileName.substring(suffixIndex + 1).toUpperCase()
  const matchList = ['nc', 'txt', 'cnc', 'mpf', 'spf', 'eia', 'mtp', 'pim', 'pdf', 'jpg', 'png', 'h']
  let result = matchList.includes(suffix.toLowerCase())
  return !result
}
/**
 * 获取文件类型，图片文件、word文件、视频文件、音乐文件、快捷方式文件
 * fileName 文件名称
 * **/
export function getFiletype (fileName) {
  let suffixIndex = fileName.lastIndexOf('.')
  let extname = fileName.substring(suffixIndex + 1).toLowerCase()
  let word = ['doc', 'docx', 'rtf']
  let excel = ['xlsx', 'xls', 'csv']
  let ppt = ['ppt', 'pptx', 'pps', 'pot', 'ppa']
  let pdf = ['pdf']
  let link = ['lnk']
  let video = ['avi', 'mov', 'rm', 'ram', '3gp', 'flv', 'mpg', 'mpeg', 'rmvb', 'mp4', 'm2v', 'mkv',
    'mov', 'swf']
  let audio = ['mp3', 'mp2', 'mp1', 'wav', 'wmv', 'wma', 'amr', 'mid', 'cda', 'vqf']
  let img = ['png', 'jpg', 'jpeg', 'bmp', 'rle', 'dib', 'gif', 'psd', 'pdd', 'ai', 'pic', 'did',
    'tif', 'tiff', 'tga', 'ico', 'raw']
  // 压缩包
  let compress = ['rar', 'zip', 'arj', 'z']
  if (word.indexOf(extname) > -1) {
    return 'word'
  }
  if (excel.indexOf(extname) > -1) {
    return 'excel'
  }
  if (ppt.indexOf(extname) > -1) {
    return 'ppt'
  }
  if (pdf.indexOf(extname) > -1) {
    return 'pdf'
  }
  if (link.indexOf(extname) > -1) {
    return 'link'
  }
  if (video.indexOf(extname) > -1) {
    return 'video'
  }
  if (audio.indexOf(extname) > -1) {
    return 'audio'
  }
  if (img.indexOf(extname) > -1) {
    return 'img'
  }
  if (compress.indexOf(extname) > -1) {
    return 'compress'
  }
  return 'other'
}

/**
 * 获取父元素距离浏览器的距离
 * obj 当前dom元素
 * **/
export function getOffsetLeft (obj) {
  let tmp = obj.offsetLeft
  let node = obj.offsetParent
  while (node !== null) {
    tmp += node.offsetLeft
    node = node.offsetParent
  }
  return tmp
}

export function getOffsetTop (obj) {
  let tmp = obj.offsetTop
  let node = obj.offsetParent
  while (node !== null) {
    tmp += node.offsetTop
    node = node.offsetParent
  }
  return tmp
}

const TIME_PATTERN = 'HH:mm:ss'
const DATE_PATTERN = 'YYYY-MM-DD'
const DATETIME_PATTERN = 'YYYY-MM-DD HH:mm'

export function formatTime (date) {
  return quasarDateUtils.formatDate(new Date(date), TIME_PATTERN)
}

export function formatDate (date) {
  return quasarDateUtils.formatDate(new Date(date), DATE_PATTERN)
}

export function formatDatetime (date) {
  return quasarDateUtils.formatDate(new Date(date), DATETIME_PATTERN)
}

/**
 * 实时加工数据报文解析器
 */
export class RealtimeProcessingDataMsgParser {
  /**
   * 设备轴信息元数据
   *
   * @type {Map<string, { name, type, routeNum, propName }>}
   * @property {string} name 轴名称
   * @property {('feedAxis'|'spindle')} type 轴类型
   * @property {number} routeNum 路线序号，从 0 起计
   * @property {string} propName 属性名称
   * @private
   */
  _axesMetadata

  /**
   * construct a message parser
   *
   * @param deviceInfo 系统信息
   */
  constructor (deviceInfo) {
    this._axesMetadata = RealtimeProcessingDataMsgParser._getDeviceAxesMetadata(deviceInfo)
  }

  /**
   * 从设备信息中获取描述设备所有轴的信息的元数据
   *
   * @param {{axes, spindles}[]} deviceInfo 设备信息
   * @returns {Map<string, Object>} 设备轴信息元数据
   * @private
   */
  static _getDeviceAxesMetadata (deviceInfo) {
    // 遍历所有路线，列举设备所有轴
    let allAxes = []
    deviceInfo.forEach((route, routeNum) => {
      // 将所有轴信息拍平
      // 向轴信息中添加 "轴类型" & "路线序号" 字段
      let axesEachRoute = []
      axesEachRoute.push(...route.axes.map(o => ({ type: 'feedAxis', routeNum, ...o })))
      axesEachRoute.push(...route.spindles.map(o => ({ type: 'spindle', routeNum, ...o })))

      allAxes.push(...axesEachRoute)
    })

    // 与轴有关的属性的名称
    const axisPropNames = ['load', 'act', 'speed', 'current', 'speedOvr']

    // 遍历所有轴，建立元数据 map
    // 轴名称 + 轴属性名称 = 报文 key
    let metadata = new Map()
    allAxes.forEach(axis => {
      axisPropNames.forEach(propName => {
        let key = axis.name + propName
        metadata.set(key, { propName, ...axis })
      })
    })

    return metadata
  }

  /**
   * 将原始报文解析成键值对
   *
   * @param {string} rawMsg 原始报文
   * @returns {Map<string, string>} 键值对形式的报文
   * @private
   */
  static _rawMsgToMap (rawMsg) {
    let rawMsgArr = rawMsg.split('|')

    // 首项为时间，去除之
    rawMsgArr.shift()

    let map = new Map()
    // 奇数位为 key，偶数位为 value
    for (let i = 0; i < rawMsgArr.length; i += 2) {
      const key = rawMsgArr[i]
      const value = rawMsgArr[i + 1]
      map.set(key, value)
    }

    return map
  }

  /**
   * 根据轴的类型，获取其在路线数据中对应的字段名
   *
   * @param {'feedAxis'|'spindle'} axisType 轴类型
   * @return {'feedAxes'|'spindles'} 路线数据中的字段名
   * @throws 若传入的轴类型不在允许的范围内，则抛出异常
   * @private
   */
  static _axisTypeToRouteFieldName (axisType) {
    const field = { feedAxis: 'feedAxes', spindle: 'spindles' }[axisType]
    if (!field) {
      throw new Error('轴类型不合法')
    }
    return field
  }

  /**
   * 根据进给轴数据的单位，获取其在进给轴数据中对应的字段名
   *
   * @param {'mm'|'degree'} feedAxisUnit 进给轴数据的单位名
   * @return {'straight'|'rotating'} 进给轴数据中的字段名
   * @throws 若传入的轴数据单位不在允许的范围内，则抛出异常
   * @private
   */
  static _feedAxisUnitToFieldName (feedAxisUnit) {
    const field = { mm: 'straight', degree: 'rotating' }[feedAxisUnit]
    if (!field) {
      throw new Error('进给轴数据单位不合法')
    }
    return field
  }

  /**
   * 初始化路线数据数组中的一条路线数据
   * <p>
   * 若该条路线已经有值，则不对其进行任何修改，直接返回该路线数据
   *
   * @param {{}[]} routes 路线数据数组
   * @param {number} index 待初始化的路线的下标
   * @param {{}} [axisMetadata] 轴信息源数据，若指定了该字段，且路线数据中没有该轴，
   * 则会初始化该轴数据
   * @return {{}} route 该条路线数据
   * @private
   */
  static _initSingleRoute (routes, index, axisMetadata) {
    // 若无该路线，初始化该路线
    let route = routes[index]
    if (!route) {
      routes[index] = { feedAxes: { straight: {}, rotating: {} }, spindles: {} }
      route = routes[index]
    }

    // 若无需初始化轴，则直接返回该路线
    if (!axisMetadata) {
      return route
    }

    // 转换轴类型到路线中的字段名
    const axesField = this._axisTypeToRouteFieldName(axisMetadata.type)
    const isFeedAxis = axisMetadata.type === 'feedAxis'

    // 若无该轴，初始化该轴
    if (isFeedAxis) {
      // 若为进给轴，根据轴数据单位获取对应字段名，并挂载到 route.feedAxes.xxx 下
      // 转换进给轴单位到进给轴数据中的字段名
      const feedAxisField = this._feedAxisUnitToFieldName(axisMetadata.unit)
      route[axesField][feedAxisField][axisMetadata.name] =
        route[axesField][feedAxisField][axisMetadata.name] || {}
    } else {
      // 否则为主轴，直接挂载到 route.spindles 下
      route[axesField][axisMetadata.name] = route[axesField][axisMetadata.name] || {}
    }

    return route
  }

  /**
   * 将报文中的一项作为轴数据解析，并将解析出的数据插入到路线数组中
   *
   * @param key 报文数据的 key
   * @param value 报文数据的 value
   * @param routes 路线数组
   * @private
   */
  _parseAsAxisData (key, value, routes) {
    const metadata = this._axesMetadata.get(key)

    const route = RealtimeProcessingDataMsgParser._initSingleRoute(routes, metadata.routeNum,
      metadata)

    const axesField = RealtimeProcessingDataMsgParser._axisTypeToRouteFieldName(metadata.type)
    const isFeedAxis = metadata.type === 'feedAxis'

    if (isFeedAxis) {
      const feedAxisField = RealtimeProcessingDataMsgParser._feedAxisUnitToFieldName(metadata.unit)
      route[axesField][feedAxisField][metadata.name][metadata.propName] = value
    } else {
      route[axesField][metadata.name][metadata.propName] = value
    }
  }

  /**
   * 将报文中的一项作为设备数据解析，并将解析出的数据插入到路线数组中
   *
   * @param key 报文数据的 key
   * @param value 报文数据的 value
   * @param routes 路线数组
   * @private
   */
  _parseAsDeviceData (key, value, routes) {
    const suffixNumberRegex = /\d+$/g

    // 取原 key 中的数字作为路线序号
    // 若原 key 中无数字，则视为第一条路线
    let routeNum = key.match(suffixNumberRegex)
    routeNum = routeNum ? routeNum[0] - 1 : 0

    const route = RealtimeProcessingDataMsgParser._initSingleRoute(routes, routeNum)

    let field = key.replace(suffixNumberRegex, '')
    route[field] = value
  }

  /**
   * 解析原始报文，获取设备所有路线的数据
   *
   * @param rawMsg 原始报文
   * @returns {{}[]} 设备所有路线的数据
   */
  parse (rawMsg) {
    const msg = RealtimeProcessingDataMsgParser._rawMsgToMap(rawMsg)

    let routes = []

    // 遍历报文每一项
    msg.forEach((value, key) => {
      // 若该项是时间，忽略之
      if (key === 'time') {
        return
      }

      // 若该项是轴数据，则按轴数据处理
      // 否则按设备数据处理
      (this._axesMetadata.has(key) && !this._parseAsAxisData(key, value, routes)) ||
      this._parseAsDeviceData(key, value, routes)
    })

    return routes
  }
}

// 根据秒数获取时分秒
export function secondToString (num) {
  let second = parseInt(num) // 秒
  let minute = 0 // 分
  let hour = 0 // 小时
  if (second >= 60) { // 如果秒数大于60，将秒数转换成整数
    // 获取分钟，除以60取整数，得到整数分钟
    minute = parseInt(second / 60)
    // 获取秒数，秒数取佘，得到整数秒数
    second = parseInt(second % 60)
    // 如果分钟大于60，将分钟转换成小时
    if (minute >= 60) {
      // 获取小时，获取分钟除以60，得到整数小时
      hour = parseInt(minute / 60)
      // 获取小时后取佘的分，获取分钟除以60取佘的分
      minute = parseInt(minute % 60)
    }
  }
  let string = ''
  if (Number(hour) > 0) {
    string += hour + '时'
  }
  if (Number(minute) > 0) {
    string += minute + '分'
  }
  string += second + '秒'
  return string
}

export function secondToHour (num) {
  const second = parseInt(num) // 秒
  if (isNaN(second)) return 0
  return second / (60 * 60)
}

// 解析token
export function translateToken (token) {
  let tokenArr = token.split('.')
  let deCodeToken = decodeURIComponent(
    escape(atob(tokenArr[1].replace(/-/g, '+').replace(/_/g, '/'))))
  let parseToken = jsonBigint({ storeAsString: true }).parse(deCodeToken)
  return parseToken
}

// 判断上传选择的文件是否符合类型要求
export function fileTypeHoldUp (fileName, targetTypeArr) {
  const index = fileName.lastIndexOf('.')
  const suffix = fileName.substring(index)
  if (!targetTypeArr.includes(suffix.toLowerCase())) {
    Dialog.create({
      title: `<p class="dialog-notice-title">
        <i class="iconfont icontubiao-52" ></i> 提示</p>`,
      message: `请选择${targetTypeArr.toString()}类型的文件`,
      html: true,
      ok: {
        unelevated: true,
        label: '确认'
      }
    })
  }
  return targetTypeArr.includes(suffix)
}

// 秒转时分秒字符串
export function secondToDate (result) {
  const h = Math.floor(result / 3600)
  const m = Math.floor((result / 60 % 60))
  const s = Math.floor((result % 60))
  let date = ''
  date = h + 'h' + m + 'm' + s + 's'
  return date
}

/**
 * Moment.js 工具类
 *
 * @class
 */
export class MomentJsUtils {
  /** @private */ static _MILLI_PER_SEC = 1000
  /** @private */ static _SEC_PER_MIN = 60
  /** @private */ static _MIN_PER_HOUR = 60
  /** @private */ static _HOUR_PER_DAY = 24
  /** @private */ static _DAY_PER_MONTH = 30
  /** @private */ static _MONTH_PER_YEAR = 12
  /** @private */ static _UNITS_MAP = {
    S: { name: '毫秒', sort: 0 },
    s: { name: '秒', sort: 1 },
    m: { name: '分钟', sort: 2 },
    h: { name: '小时', sort: 3 },
    d: { name: '天', sort: 4 },
    M: { name: '个月', sort: 5 },
    y: { name: '年', sort: 6 }
  }

  /**
   * 将 moment.js 的 {@link moment#Duration Duration} 对象转换为人类可读的文字，并保留精度
   * <p>
   * 本方法不同于 {@link moment#Duration#humanize Duration#humanize}，humanize
   * 方法中每个时间单位都存在阈值，当超出阈值时，humanize() 的结果将舍弃精度、提升到下一级单位。
   * e.g., 实际持续时长 45min 时，humanize() 的结果为 "an hour"；实际持续时长 5s 时，humanize()
   * 的结果为 "a few seconds"
   * <p>
   * 因此对于需要精确时长的场景，应使用本方法进行 humanize
   *
   * @param {moment#Duration} duration Moment.js 的 Duration 对象
   * @param {string} [precision='S'] 精度，最终结果将精确到该精度，可用的精度见下表
   * <p>
   * <table>
   *   <tr><th style="margin-right: 10px;">精度</th><th>含义</th></tr>
   *   <tr><td>S</td><td>毫秒</td></tr>
   *   <tr><td>s</td><td>秒</td></tr>
   *   <tr><td>m</td><td>分钟</td></tr>
   *   <tr><td>h</td><td>小时</td></tr>
   *   <tr><td>d</td><td>天</td></tr>
   *   <tr><td>M</td><td>个月</td></tr>
   *   <tr><td>y</td><td>年</td></tr>
   *   </table>
   * @return {string} 人类可读的持续时间文字
   */
  static humanizeDurationPrecisely (duration, precision = 'S') {
    if (!moment.isDuration(duration)) {
      throw new Error('Argument duration is not a moment.js Duration instance.')
    }

    const UNITS = MomentJsUtils._UNITS_MAP
    if (UNITS[precision] === undefined) {
      throw new Error('Illegal precision, see the document for more information.')
    }

    const parts = [
      { unit: UNITS.y, value: Math.floor(duration.asYears()) },
      { unit: UNITS.M, value: Math.floor(duration.asMonths()) % MomentJsUtils._MONTH_PER_YEAR },
      { unit: UNITS.d, value: Math.floor(duration.asDays()) % MomentJsUtils._DAY_PER_MONTH },
      { unit: UNITS.h, value: Math.floor(duration.asHours()) % MomentJsUtils._HOUR_PER_DAY },
      { unit: UNITS.m, value: Math.floor(duration.asMinutes()) % MomentJsUtils._MIN_PER_HOUR },
      { unit: UNITS.s, value: Math.floor(duration.asSeconds()) % MomentJsUtils._SEC_PER_MIN },
      { unit: UNITS.S, value: Math.floor(duration.asMilliseconds()) % MomentJsUtils._MILLI_PER_SEC }
    ]

    const mediator = []
    const precisionSort = UNITS[precision].sort
    for (const part of parts) {
      part.unit.sort >= precisionSort && part.value && mediator.push(part.value + part.unit.name)
    }

    if (mediator.length === 0) {
      return 0 + UNITS[precision].name
    }

    return mediator.join('').trim()
  }
}

// mqtt错误消息弹框
export function mqttErrMessage (code) {
  Dialog.create({
    title: `<p class="dialog-notice-title">
<i class="iconfont icontubiao-52" ></i> 提示</p>`,
    message: boxErr[code],
    html: true,
    ok: {
      unelevated: true,
      label: '确认'
    }
  })
}

// 文件大小格式化
export function sizeFormat (bytes) {
  if (bytes < 0) {
    throw new RangeError('File size must be not less than 0.')
  }

  const unitsTable = [' B', ' KiB', ' MiB', ' GiB', ' TiB', ' PiB']

  if (bytes === 0) {
    return '0' + unitsTable[0]
  }

  // 以 1024 为底，取 size 的对数，即为 size 的单位序号
  let unitSequence = Math.log(bytes) / Math.log(1024)
  unitSequence = Math.floor(unitSequence)
  if (unitSequence >= unitsTable.length) {
    unitSequence = unitsTable.length - 1
  }

  // 计算单位转换后的数字（保留小数点后两位）
  let bytesPerUnit = unitSequence && Math.pow(1024, unitSequence)
  let numberToShow = bytesPerUnit ? bytes / bytesPerUnit : bytes
  numberToShow = Math.round(numberToShow * 100) / 100

  return numberToShow + unitsTable[unitSequence]
}

/**
 * 返回当前时间与开始时间相差的秒数
 * @param {string} stratTime 开始时间 格式 YYYY-MM-DD HH:mm:ss
 * @return {string} diff 相差秒数
 */
export function diffSecond (stratTime) {
  let currentTime = moment(new Date()).format('YYYY-MM-DD HH:mm:ss')
  let diff = moment(currentTime).diff(stratTime, 'seconds')
  return diff
}

/**
 * 纯文本 Base64 编码器
 * <p>
 * 用于将字符串按指定的字符编码编为 Base64 的形式
 */
export class Base64Encoder {
  /**
   * 缺省字符编码
   *
   * @type {string}
   * @private
   */
  static _defaultEncoding = 'UTF-8'

  /**
   * 编码器当前所使用的字符编码
   *
   * @private
   */
  _encoding

  /**
   * 构造编码器
   *
   * @param {string} [encoding=UTF-8] 字符编码
   */
  constructor (encoding = Base64Encoder._defaultEncoding) {
    this._encoding = encoding
  }

  /**
   * 切换字符编码
   *
   * @param {string} encoding 新的字符编码
   * @returns {Base64Encoder} 返回自身，便于链式调用
   */
  withEncoding (encoding) {
    this._encoding = encoding
    return this
  }

  /**
   * 将文本按指定的字符编码编为 Base64
   * <p>
   * 通过 {@link #withEncoding} 可以指定字符编码
   *
   * @param {string} text 待编码的文本
   * @returns {string} 按指定字符编码编得的 Base64
   */
  encode (text) {
    // 视 encoding 而定，对 text 进行 SBCS（single-byte character set）或
    // DBCS（double-byte character set）或 MBCS（multi-byte character set）编码，得到编码后的
    // 8-bit 数组（i.e., 数组的每一项为 1 byte）
    let buffer = iconv.encode(text, this._encoding)

    // 将 SBCS / DBCS / MBCS 编码的 8-bit 数组以 SBCS（ISO 8859-1）表示，确保可被 btoa 处理
    // Unicode 与 ISO 8859-1 的前 256 个码位一一对应，故可通过 String#fromCharCode 得到期望的字符串
    let sbcsString = String.fromCharCode(...buffer)

    return btoa(sbcsString)
  }
}

/**
 * 纯文本 Base64 解码器
 * <p>
 * 用于对 Base64 按指定的字符编码进行解码，并得到对应的字符串
 */
export class Base64Decoder {
  /**
   * 缺省字符编码
   *
   * @type {string}
   * @private
   */
  static _defaultEncoding = 'UTF-8'

  /**
   * 解码器当前所使用的字符编码
   *
   * @private
   */
  _encoding

  /**
   * 构造解码器
   *
   * @param {string} [encoding=UTF-8] 字符编码
   */
  constructor (encoding = Base64Decoder._defaultEncoding) {
    this._encoding = encoding
  }

  /**
   * 切换字符编码
   *
   * @param {string} encoding 新的字符编码
   * @returns {Base64Decoder} 返回自身，便于链式调用
   */
  withEncoding (encoding) {
    this._encoding = encoding
    return this
  }

  /**
   * 将 Base64 按指定的字符编码解码为文本
   * <p>
   * 通过 {@link #withEncoding} 可以指定字符编码
   *
   * @param {string} base64 待解码的 Base64 字符串
   * @returns {string} 按指定字符编码解码得到的文本
   */
  decode (base64) {
    let sbcsString = atob(base64)
    return Base64Decoder._decodeSbcsString(sbcsString, this._encoding)
  }

  // /**
  //  * 探测 Base64 的字符编码，并按探测到的字符编码解码 Base64 为文本
  //  * <p>
  //  * 本方法无视解码器自身指定的字符编码
  //  *
  //  * @param {string} base64 待解码的 Base64 字符串
  //  * @returns {{text: string, encoding: string}} 探测到的字符编码 & 解码得到的文本
  //  */
  // detectAndDecode (base64) {
  //   let sbcsString = atob(base64)
  //   let encoding = Base64Decoder._detectEncoding(sbcsString)
  //   let text = Base64Decoder._decodeSbcsString(sbcsString, encoding)
  //
  //   return { encoding, text }
  // }

  /**
   * 将 SBCS（single-byte character set）字符串按指定的字符编码解码为文本
   *
   * @param {string} sbcsString 待解码的 SBCS 字符串，其字符编码应为 ISO 8859-1
   * @param {string} encoding 解码时使用的字符编码
   * @returns {string} 按指定字符编码解码得到的文本
   * @private
   */
  static _decodeSbcsString (sbcsString, encoding) {
    let buffer = Buffer.from(sbcsString, 'latin1')
    return iconv.decode(buffer, encoding)
  }

  /**
   * 探测 SBCS（single-byte character set）字符串的真实字符编码
   *
   * @param {string} sbcsString SBCS 字符串，其字符编码应为 ISO 8859-1
   * @returns {string} 检测到的字符编码，缺省为 'UTF-8'
   * @private
   */
  // static _detectEncoding (sbcsString) {
  //   const encoding = jsChardet.detect(sbcsString).encoding || Base64Decoder._defaultEncoding
  //
  //   if (encoding === 'GB2312') {
  //     // 由于 jschardet 库自身缺陷（见 repo issues），GB2312、GBK、GB18030 均会被判定为 GB2312，
  //     // GB18030 是 GBK 的超集，GBK 又是 GB2312 的超集，若直接使用探测得到的 GB2312 对文本进行编码，
  //     // 可能导致部分字符丢失，故 hardcode 处理遇到 GB2312 就返回 GB18030。虽导致探测的编码不严谨，
  //     // 但至少保证结果最终正确
  //     return 'GB18030'
  //   }
  //
  //   return encoding
  // }
}

/**
 * 小数转为百分比
 * <p>
 * 允许指定小数点后保留的位数，舍位时采用 {@link Math#round}，结尾的零将被舍弃
 *
 * @param {number} decimal 待转换的小数
 * @param {number} precision 小数位精度
 * <p>
 * e.g. precision = 2 时，0.19486 将转为 19.49%、0.65997 将转为 66%
 * @returns {string}
 */
export function decimalToPercentage (decimal, precision) {
  const factor = Math.pow(10, precision)
  return Math.round(decimal * 100 * factor) / factor + '%'
}
/**
 * 提示
 * @param icon 图标
 * @param tips 提示语
 * @param showCancel 是否显示取消
 * @param okFun 确认事件
 */
export function showMessage (icon, tips, okFun, showCancel, onCancel) {
  Dialog.create({
    title: `<p class="dialog-notice-title"><i class="iconfont ${icon}"></i>${tips}</p>`,
    html: true,
    persistent: true,
    cancel: showCancel ? {
      unelevated: true,
      outline: true,
      label: '取消'
    } : false,
    message: '',
    ok: {
      unelevated: true,
      label: '确认'
    }
  }).onOk(() => {
    if (okFun) {
      okFun()
    }
  }).onCancel(() => {
    if (onCancel) {
      onCancel()
    }
  })
}
// 显示提示：
export function showDialog (tips) {
  let message = '<div style="overflow-y: auto;max-height: 50vh" class="scrollbar">'
  message += tips
  message += '</div>'
  Dialog.create({
    title: `<p class="dialog-notice-title"><i class="iconfont icontubiao-28"></i>提示</p>`,
    html: true,
    persistent: true,
    cancel: false,
    message: message,
    ok: {
      unelevated: true,
      label: '确认'
    }
  })
}

/**
 * 拼接动态提示语
 * @param data 提示数据
 * @param tips 提示语
 * @param joinSign 拼接符
 * @param endSign 结束符
 * @returns {string}
 */
export function getNoneTips (data, tips = '以下数据不存在', joinSign = '、', endSign = '。') {
  let newMsg = ''
  newMsg += `${tips}：<br/>`
  const dataString = _.join(data, joinSign)
  // console.log(dataString)
  newMsg += dataString + endSign
  return newMsg
}

/**
 * 日期范围搜索的时候日期转换
 * YYYY-MM-DDTHH:mm:ss
 * @param {*} dateRange Array
 * @param {*} dateType String
 */
export function setDateOSI (dateRange, isRange = true, dateType = 'YYYY-MM-DDTHH:mm:ss') {
  if (!isRange) return moment(dateRange).format(dateType)
  if (_.isEmpty(dateRange)) return [null, null]
  const newDate = [moment(dateRange[0]).format(dateType), moment(dateRange[1]).endOf('day').format(dateType)]
  return newDate
}

/**
 * 判断真假，排除0为false的情况
 * @param  {object} value
 */
const isFalsy = (value) => value === '' || value === undefined || value === null

/**
 * 对象去空
 * @param  {object} object
 */
// 在一个函数里，改变传入的对象本身是不好的
export const cleanObject = (object) => {
  const result = _.cloneDeep(object)
  Object.keys(result).forEach(key => {
    const value = result[key]
    if (isFalsy(value)) {
      delete result[key]
    }
  })
  return result
}
export function round2 (num, precision) {
  let dnum = Math.pow(10, precision)
  if (Math.floor(num * dnum * 10) % 5 === 0 && Math.floor(num * dnum * 10) === num * dnum * 10 && Math.floor(num * dnum) % 2 === 0) {
    return Math.floor(num * dnum) / dnum
  } else {
    return Math.round(num, precision)
  }
}
/**
 * 根据编号排序
 *
 * */
export function sortByAny (options = [], id) {
  const member = options.sort((a, b) => {
    const aStr = a[id].toString()
    const bStr = b[id].toString()
    return aStr.localeCompare(bStr)
  })
  return member
}

export function numberToChinese (num) {
  const chineseNums = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九']
  const chineseUnits = ['', '十', '百', '千', '万', '亿']

  if (num < 0 || num >= Math.pow(10, 12)) {
    return '不支持的数字范围'
  }

  if (num === 0) {
    return '零'
  }

  if (num <= 9) {
    return chineseNums[num]
  } else {
    const numStr = num.toString()
    let chineseStr = ''

    for (let i = 0; i < numStr.length; i++) {
      const digit = parseInt(numStr[i])
      const digitPosition = numStr.length - i - 1

      if (digit !== 0) {
        chineseStr += chineseNums[digit] + chineseUnits[digitPosition]
      } else {
        // 处理连续的零，只添加一个零
        if (i !== numStr.length - 1 && numStr[i + 1] !== '0') {
          chineseStr += chineseNums[digit]
        }
      }
    }

    // 处理十的特殊情况
    if (chineseStr === '一十') {
      chineseStr = '十'
    }

    // 处理万以上的部分
    const unitIndex = Math.ceil(numStr.length / 4) - 1
    if (unitIndex > 0) {
      chineseStr += chineseUnits[4 + unitIndex]
      const restNum = num % Math.pow(10, (unitIndex + 1) * 4)
      if (restNum === 0) {
        chineseStr += '整'
      } else {
        chineseStr += numberToChinese(restNum)
      }
    }

    return chineseStr
  }
}

export const setHasList = (newArr, hasCraft = false, hasWork = false) => {
  const member = _.cloneDeep(newArr)
  if (!hasCraft && hasWork) {
    member.splice(5, 1)
  } else if (!hasWork && hasCraft) {
    member.splice(6, 2)
  } else if (!hasCraft && !hasWork) {
    member.splice(5, 3)
  }
  return member
}
